package com.zoran.study.spring.common.advice;

import com.zoran.study.spring.common.CommonErrors;
import com.zoran.study.spring.common.RestError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/**
 * Exception advice for common exceptions.
 *
 * @author Winter Gao
 */
public abstract class DefaultExceptionAdvice extends ExceptionAdvice {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExceptionAdvice.class);

   /* @ExceptionHandler(InternalException.class)
    @ResponseBody
    public ResponseEntity<RestError> internalExceptionHandler(InternalException e, Locale locale) {
        LOGGER.error("Internal error", e);

        return toHttpResponse(HTTP_500, INTERNAL_ERROR, locale);
    }

    @ExceptionHandler(OutOfMemoryError.class)
    @ResponseBody
    public ResponseEntity<RestError> oomHandler(OutOfMemoryError e, Locale locale) {
        LOGGER.error("OOM", e);

        return toHttpResponse(HTTP_500, INTERNAL_ERROR, locale);
    }

    @ExceptionHandler(StackOverflowError.class)
    @ResponseBody
    public ResponseEntity<RestError> sofHandler(StackOverflowError e, Locale locale) {
        LOGGER.error("SOF", e);

        return toHttpResponse(HTTP_500, INTERNAL_ERROR, locale);
    }

    @ExceptionHandler(IndexOutOfBoundsException.class)
    @ResponseBody
    public ResponseEntity<RestError> ioobHandler(IndexOutOfBoundsException e, Locale locale) {
        LOGGER.error("IOOB", e);

        return toHttpResponse(HTTP_500, INTERNAL_ERROR, locale);
    }*/

    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public ResponseEntity<RestError> nullPointerExceptionHandler(NullPointerException e,
                                                                 Locale locale) {
        LOGGER.error("NPE", e);

        return toHttpResponse(HTTP_500, INTERNAL_ERROR, locale);
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public ResponseEntity<RestError> missingRequestParameterExceptionHandler(
            MissingServletRequestParameterException ex, Locale locale) {
        LOGGER.error("Missing request parameter", ex);

        return toHttpResponse(HTTP_400, CommonErrors.COMMON_PARAMETER_MISSING, locale,
                ex.getParameterName(), ex.getParameterType());
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    public ResponseEntity<RestError> messageNotReadableExceptionHandler(
            HttpMessageNotReadableException e, Locale locale) {
        LOGGER.error("Bad request payload", e);

        return toHttpResponse(HTTP_400, ILLEGAL_ARGUMENT, locale);
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseBody
    public ResponseEntity<RestError> typeMismatchExceptionHandler(
            MethodArgumentTypeMismatchException e, Locale locale) {
        LOGGER.error("Argument type mismatch", e);
        return toHttpResponse(HTTP_400, CommonErrors.COMMON_PARAMETER_TYPE_MISMATCH, locale,
                e.getName(), e.getRequiredType().getSimpleName());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ResponseEntity<RestError> constraintViolationExceptionHandler(
            MethodArgumentNotValidException e, Locale locale) {
        LOGGER.error("Constraint violation", e);

        List<ObjectError> errors = e.getBindingResult().getAllErrors();
        List<String> messages = new ArrayList<>();
        for (ObjectError oe : errors) {
            messages.add(oe.getDefaultMessage());
        }

        return toHttpResponse(HTTP_400, ILLEGAL_ARGUMENT.code(), messages);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public ResponseEntity<?> constraintViolationExceptionHandler(ConstraintViolationException e,
                                                                 Locale locale) {
        boolean response = false;
        List<String> messages = new ArrayList<>();
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        for (ConstraintViolation<?> violation : violations) {
            response = response || violation.getExecutableReturnValue() != null;
            messages.add(violation.getMessage());
        }

        if (response) {
            LOGGER.error("Illegal state {}", messages, e);
            return toHttpResponse(HTTP_500, INTERNAL_ERROR, locale);
        }

        LOGGER.error("Constraint violation", e);

        return toHttpResponse(HTTP_400, ILLEGAL_ARGUMENT.code(), messages);
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseBody
    public ResponseEntity<RestError> illegalArgumentHandler(IllegalArgumentException e,
                                                            Locale locale) {
        LOGGER.warn("Illegal argument", e);

        return toHttpResponse(HTTP_400, ILLEGAL_ARGUMENT, locale);
    }
}
